package common.kingdee.cas;

import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.authentication.NullStatelessTicketCache;
import org.springframework.security.cas.authentication.StatelessTicketCache;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;

import common.kingdee.domain.KingdeeUser;

public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean {

	private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
	private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
	private String key = "YaomingAuthenticationProvider";
	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
	private UserDetailsService userDetailsService;
	private AuthenticationDetailsSource<Authentication, String> ticketAuthenticationSource;

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
		Assert.hasText(this.key,
				"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
	}

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		if (!supports(authentication.getClass())) {
			return null;
		}

		if (authentication instanceof UsernamePasswordAuthenticationToken
				&& (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
						&& !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
								.equals(authentication.getPrincipal().toString()))) {
			return null;
		}

		if (authentication instanceof CasAuthenticationToken) {
			if (this.key.hashCode() == ((CasAuthenticationToken) authentication).getKeyHash()) {
				return authentication;
			} else {
				throw new BadCredentialsException(
						"The presented CasAuthenticationToken does not contain the expected key");
			}
		}

		if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
			throw new BadCredentialsException("Failed to provide a CAS service ticket to validate");
		}

		boolean stateless = false;

		if (authentication instanceof UsernamePasswordAuthenticationToken
				&& CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal())) {
			stateless = true;
		}

		CasAuthenticationToken result = null;

		if (stateless) {
			result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
		}

		if (result == null) {
			result = this.authenticateNow(authentication);
			result.setDetails(authentication.getDetails());
		}

		if (stateless) {
			statelessTicketCache.putTicketInCache(result);
		}

		return result;
	}

	private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
		String username = ticketAuthenticationSource.buildDetails(authentication);
		if (username == null) {
			throw new AuthenticationCredentialsNotFoundException("无效的ticket");
		}
		final KingdeeUser user = (KingdeeUser) userDetailsService.loadUserByUsername(username);
		if (user == null) {
			throw new AuthenticationCredentialsNotFoundException("无法获取用户" + username + "的详细信息");
		}
		userDetailsChecker.check(user);
		return new CasAuthenticationToken(this.key, user, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()), user, new AssertionImpl(user.getUsername()));
	}

	public void setUserDetailsService(final UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	protected String getKey() {
		return key;
	}

	public void setKey(String key) {
		this.key = key;
	}

	public StatelessTicketCache getStatelessTicketCache() {
		return statelessTicketCache;
	}

	public void setStatelessTicketCache(final StatelessTicketCache statelessTicketCache) {
		this.statelessTicketCache = statelessTicketCache;
	}

	public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
		this.authoritiesMapper = authoritiesMapper;
	}

	public void setTicketAuthenticationSource(
			AuthenticationDetailsSource<Authentication, String> ticketAuthenticationSource) {
		this.ticketAuthenticationSource = ticketAuthenticationSource;
	}

	public boolean supports(final Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication))
				|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
				|| (CasAssertionAuthenticationToken.class.isAssignableFrom(authentication));
	}
}
